///////////////////////////////////////////////////////////////////////////////////////////////////////
//	Copyright (c) 2010, Blogagic (http://blogagic.com/)
//	All rights reserved.
//
//	Redistribution and use in source and binary forms, with or without modification, 
//	are permitted provided that the following conditions are met:
//
//		- Redistributions of source code must retain the above copyright notice, this list of 
//		  conditions and the following disclaimer.
//		- Redistributions in binary form must reproduce the above copyright notice, this list 
//		  of conditions and the following disclaimer in the documentation and/or other materials 
//		  provided with the distribution.
//
//	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
//	IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
//	FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
//	CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
//	DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
//	DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
//	IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
//	OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
///////////////////////////////////////////////////////////////////////////////////////////////////////

/*
	SparklineLineChart - Sparkline display as a Line Chart.
	Copyright (C) 2010  Blogagic (http://blogagic.com/)
	Version 1.1

	Licensed under the Creative Commons BSD Licence
	http://creativecommons.org/licenses/BSD/
*/
package com.blogagic.controls
{
	import com.blogagic.controls.sparklineClasses.SparklineChart;
	
	import flash.events.Event;
	
	import mx.styles.CSSStyleDeclaration;
	import mx.styles.StyleManager;
	
	/**
	 *  Color for the line.
	 * 
	 *  @default 0x555555
	 */
	[Style(name="lineColor", type="Color", format="Color", inherit="no")]
	
	/**
	 *  Weight for the line.
	 * 
	 *  @default 1
	 */
	[Style(name="lineWeight", type="Number", inherit="no")]
	
	/**
	 *  Color for the threshold area.
	 * 
	 *  @default 0x00AA00
	 */
	[Style(name="thresholdColor", type="Color", format="Color", inherit="no")]
	
	/**
	 *  Transparency for the threshold area.
	 * 
	 *  @default 0.3
	 */
	[Style(name="thresholdAlpha", type="Number", inherit="no")]
	
	/**
	 *  Color for the min value marker.
	 * 
	 *  @default 0x008000
	 */
	[Style(name="minValueMarkerColor", type="Color", format="Color", inherit="no")]
	
	/**
	 *  Color for the max value marker.
	 * 
	 *  @default 0xFF0000
	 */
	[Style(name="maxValueMarkerColor", type="Color", format="Color", inherit="no")]
	
	/**
	 *  Color for the first value marker.
	 * 
	 *  @default 0xFFFF00
	 */
	[Style(name="firstValueMarkerColor", type="Color", format="Color", inherit="no")]
	
	/**
	 *  Color for the last value marker.
	 * 
	 *  @default 0x2963a3
	 */
	[Style(name="lastValueMarkerColor", type="Color", format="Color", inherit="no")]
	
	/**
	 *  Size for the value markers.
	 * 
	 *  @default 2
	 */
	[Style(name="markerSize", type="Number", format="Length", inherit="no")]
	
	/**
	 *  SparklineLineChart displays a Sparkline as a Line Chart.
	 *  
	 *  <p><code>SparklineLineChart</code> implements <code>IListItemRenderer</code>
	 *  and <code>IDropInListItemRenderer</code> allowing to use <code>SparklineLineChart</code>
	 *  as an item renderer inside a data grid or list control.  
	 *  <code>SparklineLineChart</code> can also be used as a regular standalone control.</p>
	 * 
	 *  @mxml
	 *
	 *  <p>The SparklineLineChart class inherits all the properties and styles of its parent 
	 *  classes and adds the following:</p>
	 *
	 *  <pre>
	 *  &lt;mx:SparklineLineChart
	 *    <b>Properties</b>
	 *    drawMinValueMarker="true"
	 *    drawMaxValueMarker="true"
	 *    drawFirstValueMarker="false"
	 *    drawLastValueMarker="true"
	 * 
	 *    <b>Styles</b>
	 *    lineColor="0x555555"
	 *    lineWeight="1"
	 *    thresholdColor="0x008000"
	 *    thresholdAlpha="0.3"
	 *    minValueMarkerColor="0x008000"
	 *    maxValueMarkerColor="0xFF0000"
	 *    firstValueMarkerColor="0xFFFF00"
	 *    lastValueMarkerColor="0x2963a3"
	 *    markerSize="2"
	 * 
	 *    <b>Events</b>
	 *    drawMinValueMarkerChanged="<i>No default</i>"
	 *    drawMaxValueMarkerChanged="<i>No default</i>"
	 *    drawFirstValueMarkerChanged="<i>No default</i>"
	 *    drawLastValueMarkerChanged="<i>No default</i>"
	 *  /&gt;
	 *  </pre>
	 *
	 *  @author JYC
	 */
	public class SparklineLineChart extends SparklineChart
	{		
		//--------------------------------------------------------------------------
		//
		//  Properties
		//
		//--------------------------------------------------------------------------
		
		//----------------------------------
		//  Markers
		//----------------------------------
		
		
		/**
		 *  @private
		 */
		private var _drawMinValueMarker:Boolean = true;
		[Inspectable(category="Marker", type="Boolean", defaultValue="true")]
		
		/**
		 *  A flag that indicates whether a marker must be drawn at the minimum value position.
		 * 
		 *  <p>This property can be used as the source for data binding. When this
		 *  property is modified, it dispatches the <code>drawMinValueMarkerChanged</code> event.</p>
		 * 
		 *  @default true
		 */
		[Bindable("drawMinValueMarkerChanged")]
		public function get drawMinValueMarker():Boolean
		{
			return _drawMinValueMarker;
		}
		
		/**
		 *  @private
		 */
		public function set drawMinValueMarker(value:Boolean):void
		{
			if (_drawMinValueMarker != value)
			{
				_drawMinValueMarker = value;
				invalidateDisplayList();
				
				dispatchEvent(new Event("drawMinValueMarkerChanged"));
			}
		}

		/**
		 *  @private
		 */
		private var _drawMaxValueMarker:Boolean = true;
		[Inspectable(category="Marker", type="Boolean", defaultValue="true")]
		
		/**
		 *  A flag that indicates whether a marker must be drawn at the maximum value position.
		 * 
		 *  <p>This property can be used as the source for data binding. When this
		 *  property is modified, it dispatches the <code>drawMaxValueMarkerChanged</code> event.</p>
		 * 
		 *  @default true
		 */
		[Bindable("drawMaxValueMarkerChanged")]
		public function get drawMaxValueMarker():Boolean
		{
			return _drawMaxValueMarker;
		}

		/**
		 *  @private
		 */
		public function set drawMaxValueMarker(value:Boolean):void
		{
			if (_drawMaxValueMarker != value)
			{
				_drawMaxValueMarker = value;
				invalidateDisplayList();
				
				dispatchEvent(new Event("drawMaxValueMarkerChanged"));
			}
		}

		/**
		 *  @private
		 */
		private var _drawFirstValueMarker:Boolean = true;
		[Inspectable(category="Marker", type="Boolean", defaultValue="true")]
		
		/**
		 *  A flag that indicates whether a marker must be drawn at the first value position.
		 * 
		 *  <p>This property can be used as the source for data binding. When this
		 *  property is modified, it dispatches the <code>drawFirstValueMarkerChanged</code> event.</p>
		 * 
		 *  @default true
		 */
		[Bindable("drawFirstValueMarkerChanged")]
		public function get drawFirstValueMarker():Boolean
		{
			return _drawFirstValueMarker;
		}
		
		/**
		 *  @private
		 */
		public function set drawFirstValueMarker(value:Boolean):void
		{
			if (_drawFirstValueMarker != value)
			{
				_drawFirstValueMarker = value;
				invalidateDisplayList();
				
				dispatchEvent(new Event("drawFirstValueMarkerChanged"));
			}
		}

		/**
		 *  @private
		 */
		private var _drawLastValueMarker:Boolean = true;
		[Inspectable(category="Marker", type="Boolean", defaultValue="true")]

		/**
		 *  A flag that indicates whether a marker must be drawn at the last value position.
		 * 
		 *  <p>This property can be used as the source for data binding. When this
		 *  property is modified, it dispatches the <code>drawLastValueMarkerChanged</code> event.</p>
		 * 
		 *  @default true
		 */
		[Bindable("drawLastValueMarkerChanged")]
		public function get drawLastValueMarker():Boolean
		{
			return _drawLastValueMarker;
		}

		/**
		 *  @private
		 */
		public function set drawLastValueMarker(value:Boolean):void
		{
			if (_drawLastValueMarker != value)
			{
				_drawLastValueMarker = value;
				invalidateDisplayList();
				
				dispatchEvent(new Event("drawLastValueMarkerChanged"));
			}
		}
		
		//--------------------------------------------------------------------------
		//
		//  Constructor
		//
		//--------------------------------------------------------------------------
		
		/**
		 *  Constructor
		 */
		public function SparklineLineChart()
		{
			super();
		}	
		
		//--------------------------------------------------------------------------
		//
		//  Styles
		//
		//--------------------------------------------------------------------------
		
		/**
		 *  @private
		 */
		private static var classConstructed:Boolean = classConstruct();
		
		/**
		 *  @private
		 *  Styles initialization.
		 */
		private static function classConstruct():Boolean {
			
			var style:CSSStyleDeclaration = StyleManager.getStyleDeclaration("SparklineLineChart");
			if (!style) {
				style = new CSSStyleDeclaration();
				
				style.defaultFactory = function():void {
					this.lineColor = 0x555555;
					this.lineWeight = 1;
					this.thresholdColor = 0x00AA00;
					this.thresholdAlpha = 0.3;
					this.minValueMarkerColor = 0x008000;
					this.maxValueMarkerColor = 0xFF0000;
					this.firstValueMarkerColor = 0xFFFF00;
					this.lastValueMarkerColor = 0x2963a3;
					this.markerSize = 2;
				};
				StyleManager.setStyleDeclaration("SparklineLineChart", style, true);
			}
			else {
				if (style.getStyle("lineColor") == undefined) {
					style.setStyle("lineColor", "0x555555");	        		
				}
				if (style.getStyle("lineWeight") == undefined) {
					style.setStyle("lineWeight", "1");	        		
				}
				if (style.getStyle("thresholdColor") == undefined) {
					style.setStyle("thresholdColor", "0x00AA00");	        		
				}
				if (style.getStyle("thresholdAlpha") == undefined) {
					style.setStyle("thresholdAlpha", "0.3");	        		
				}
				if (style.getStyle("minValueMarkerColor") == undefined) {
					style.setStyle("minValueMarkerColor", "0x008000");	        		
				}
				if (style.getStyle("maxValueMarkerColor") == undefined) {
					style.setStyle("maxValueMarkerColor", "0xFF0000");	        		
				}
				if (style.getStyle("firstValueMarkerColor") == undefined) {
					style.setStyle("firstValueMarkerColor", "0xFFFF00");	        		
				}
				if (style.getStyle("lastValueMarkerColor") == undefined) {
					style.setStyle("lastValueMarkerColor", "0x2963a3");	        		
				}
				if (style.getStyle("markerSize") == undefined) {
					style.setStyle("markerSize", "2");	        		
				}
			}
			
			return true;        
		}
		
		/**
		 *  @private
		 */
		override public function styleChanged(styleProp:String):void {
			super.styleChanged(styleProp);
			
			switch (styleProp) {
				case "lineColor":
				case "thresholdColor":
				case "thresholdAlpha":
				case "minValueMarkerColor":
				case "maxValueMarkerColor":
				case "firstValueMarkerColor":
				case "lastValueMarkerColor":
					invalidateDisplayList();
					break;
				case "lineWeight":
				case "markerSize":
					// scale factors depend on getPadding() that integrates lineWeight and markerSize
					_verticalScaleDirty = true;
					_horizontalScaleDirty = true;
					invalidateProperties();
					invalidateDisplayList();
					break;
			}
		}
		
		//--------------------------------------------------------------------------
		//
		//  Methods
		//
		//--------------------------------------------------------------------------
		
		/**
		 *  Computes and returns the padding to be applied around the sparkline chart.
		 *  
		 *  <p>Global padding value includes <code>padding</code> and Max(<code>lineWeight</code>,
		 *  <code>markerSize</code>).</p>
		 * 
		 *  @return Padding value in pixels.
		 */
		override protected function getPadding():Number {
			return getStyle("padding") + 0.5 * Math.max(getStyle("lineWeight"), getStyle("markerSize"));
		}  	
		
		/**
		 *  Draws the Sparkline as a Line Chart.
		 * 
		 *  @param chartWidth Width of the chart area.
		 *  @param chartHeight Height of the chart area.
		 */
		override protected function drawChart(chartWidth:Number, chartHeight:Number):void
		{
			var padding:Number = getPadding();
			
			if (showThreshold)
				drawThreshold(chartWidth, chartHeight, padding);
			
			var i:uint = 0;
			var l:uint = dataProvider.length;
			
			if (l <= 0)
				return;
				
			var skip:uint = Math.max(0, l-maxPlots);
			var hole:Boolean = true;           
			
			graphics.lineStyle(getStyle("lineWeight"), getStyle("lineColor"));
			
			for (i = skip; i < l; i++)
			{
				computePosition(i, skip, chartWidth, chartHeight, padding);
				
				// Take care of missing values (when xField or yField != "")
				if (isNaN(position.x) || isNaN(position.y))
					hole = true;
				else {
					if (hole) {
						graphics.moveTo(position.x, position.y);
						hole = false;
					}
					else
						graphics.lineTo(position.x, position.y);
				}
			}
			
			if (drawMinValueMarker || drawMaxValueMarker || drawFirstValueMarker || drawLastValueMarker)
				drawMarkers(skip, chartWidth, chartHeight, padding);
		}
		
		/**
		 *  Draws the Threshold area.
		 * 
		 *  <p>Draws and fills a rectangle from the top to the threshold value or from
		 *  the threshold value to the bottom depending on the <code>upperIsFault</code>
		 *  property.</p>
		 * 
		 *  @param chartWidth Width of the chart area.
		 *  @param chartHeight Height of the chart area.
		 *  @param padding Padding to consider around the sparkline chart.
		 */
		protected function drawThreshold(chartWidth:Number, chartHeight:Number, padding:Number):void
		{
			// Check if threshold is visible
			if ((upperIsFault && (threshold < yMin)) || (!upperIsFault && (threshold > yMax)))
				return;
			
			var y:Number = getYPosition(threshold, chartHeight, padding);
			
			graphics.lineStyle();
			graphics.beginFill(getStyle("thresholdColor"), getStyle("thresholdAlpha"));
			
			if (upperIsFault) {
				graphics.drawRect(padding, y, chartWidth-2*padding, chartHeight-y-padding);
			}
			else {
				graphics.drawRect(padding, padding, chartWidth-2*padding, y-padding);
			}
			
			graphics.endFill();
		}
		
		/**
		 *  Draws the markers.
		 * 
		 *  <p>Draws and fills a circle for each visible marker.</p>
		 * 
		 *  @param skip Number of elements of the dataProvider to be skipped.
		 *  @param chartWidth Width of the chart area.
		 *  @param chartHeight Height of the chart area.
		 *  @param padding Padding to consider around the sparkline chart.
		 */
		protected function drawMarkers(skip:uint, chartWidth:Number, chartHeight:Number, padding:Number):void
		{
			var markerSize:Number = getStyle("markerSize");
			
			graphics.lineStyle();
			
			if (drawMinValueMarker) {
				computePosition(posMinValue, skip, chartWidth, chartHeight, padding);
				
				if (!isNaN(position.x) && !isNaN(position.y)) {
					var colorMin:Number = getStyle("minValueMarkerColor");
					graphics.beginFill(colorMin);
					graphics.drawCircle(position.x, position.y, markerSize);
				}
			}
			
			if (drawMaxValueMarker) {
				computePosition(posMaxValue, skip, chartWidth, chartHeight, padding);
				if (!isNaN(position.x) && !isNaN(position.y)) {
					var colorMax:Number = getStyle("maxValueMarkerColor");
					graphics.beginFill(colorMax);
					graphics.drawCircle(position.x, position.y, markerSize);
				}
			}
			
			if (drawFirstValueMarker) {
				computePosition(skip, skip, chartWidth, chartHeight, padding);
				if (!isNaN(position.x) && !isNaN(position.y)) {
					var colorFirst:Number = getStyle("firstValueMarkerColor");
					graphics.beginFill(colorFirst);
					graphics.drawCircle(position.x, position.y, markerSize);
				}
			}
			
			if (drawLastValueMarker) {
				var l:uint = dataProvider.length;
				computePosition(l-1, skip, chartWidth, chartHeight, padding);
				if (!isNaN(position.x) && !isNaN(position.y)) {
					var colorLast:Number = getStyle("lastValueMarkerColor");
					graphics.beginFill(colorLast);
					graphics.drawCircle(position.x, position.y, markerSize);
				}
			}
			
			graphics.endFill();
		}
	}
}
